import React from 'react';
import { render, screen, userEvent } from '@mongodb-js/testing-library-compass';
import { expect } from 'chai';
import sinon from 'sinon';

import { InteractivePopover } from './interactive-popover';

const innerContentTestId = 'testing-inner-content';

function renderPopover(
  props?: Partial<React.ComponentProps<typeof InteractivePopover>>
) {
  return render(
    <InteractivePopover
      className=""
      open={false}
      hideCloseButton={props?.hideCloseButton}
      customFocusTrapFallback={`#${innerContentTestId}`}
      setOpen={() => {}}
      trigger={({
        onClick,
        ref,
        children,
      }: {
        onClick: React.MouseEventHandler<HTMLButtonElement>;
        ref: React.Ref<HTMLButtonElement>;
        children?: React.ReactNode;
      }) => (
        <>
          <button
            data-testid="trigger-button"
            type="button"
            ref={ref}
            onClick={onClick}
          >
            Trigger Button Text
          </button>
          {children}
        </>
      )}
      {...props}
    >
      <>
        <button
          type="button"
          onClick={() => {}}
          id={innerContentTestId}
          data-testid={innerContentTestId}
        >
          Action Button
        </button>
        <div>inner content</div>
      </>
    </InteractivePopover>
  );
}

describe('InteractivePopover Component', function () {
  let addedElement: HTMLDivElement | undefined;
  afterEach(function () {
    if (addedElement) {
      try {
        document.body.removeChild(addedElement);
      } finally {
        addedElement = undefined;
      }
    }
  });

  it('when open it should show the popover content', function () {
    renderPopover({
      open: true,
    });
    expect(screen.getByTestId(innerContentTestId)).to.be.visible;
  });

  it('when closed it should not show the popover content', function () {
    renderPopover({
      open: false,
    });
    expect(screen.queryByTestId(innerContentTestId)).to.not.exist;
  });

  it('should render the trigger', function () {
    renderPopover({
      open: false,
    });
    const button = screen.getByRole('button');
    expect(button).to.be.visible;
    expect(screen.getByText('Trigger Button Text')).to.be.visible;
  });

  it('when closed and the trigger is clicked it should call to open', function () {
    const openSpy = sinon.fake();

    renderPopover({
      open: false,
      setOpen: openSpy,
    });
    expect(openSpy.calledOnce).to.be.false;

    const button = screen.getByText('Trigger Button Text');
    button.click();
    expect(openSpy.calledOnce).to.be.true;
    expect(openSpy.firstCall.firstArg).to.equal(true);
  });

  it('when open and the trigger is clicked it should call to close', function () {
    const openSpy = sinon.fake();

    renderPopover({
      open: true,
      setOpen: openSpy,
    });
    expect(openSpy.calledOnce).to.be.false;

    const button = screen.getByText('Trigger Button Text');
    button.click();
    expect(openSpy.calledOnce).to.be.true;
    expect(openSpy.firstCall.firstArg).to.equal(false);
  });

  it('when open and the close is clicked it should call to close', function () {
    const openSpy = sinon.fake();

    renderPopover({
      open: true,
      setOpen: openSpy,
    });
    expect(openSpy.calledOnce).to.be.false;

    const button = screen.getByTestId('interactive-popover-close-button');
    button.click();
    expect(openSpy.calledOnce).to.be.true;
    expect(openSpy.firstCall.firstArg).to.equal(false);
  });

  it('when open with a custom button, the general close button is not visible', function () {
    renderPopover({
      open: true,
      hideCloseButton: true,
    });

    expect(screen.queryByTestId('interactive-popover-close-button')).to.not
      .exist;
  });

  it('should close when escape key is pressed', function () {
    const openSpy = sinon.fake();

    renderPopover({
      open: true,
      setOpen: openSpy,
    });

    expect(openSpy.calledOnce).to.be.false;
    userEvent.keyboard('{Escape}');
    expect(openSpy.calledOnce).to.be.true;
    expect(openSpy.firstCall.firstArg).to.equal(false);
  });

  it('should close when clicking outside the popover', function () {
    const openSpy = sinon.fake();

    renderPopover({
      open: true,
      setOpen: openSpy,
    });

    addedElement = document.createElement('div');
    document.body.appendChild(addedElement);

    expect(openSpy.calledOnce).to.be.false;
    userEvent.click(addedElement);

    expect(openSpy.calledOnce).to.be.true;
    expect(openSpy.firstCall.firstArg).to.equal(false);
  });

  it('should not close when clicking on contained elements', function () {
    const openSpy = sinon.fake();

    addedElement = document.createElement('div');
    addedElement.className = 'contained-element';
    document.body.appendChild(addedElement);

    renderPopover({
      open: true,
      setOpen: openSpy,
      containedElements: ['.contained-element'],
    });

    userEvent.click(addedElement);
    expect(openSpy.called).to.be.false;
  });

  it('should not close when clicking inside the popover content', function () {
    const openSpy = sinon.fake();

    renderPopover({
      open: true,
      setOpen: openSpy,
    });

    const innerButton = screen.getByTestId(innerContentTestId);
    userEvent.click(innerButton);

    expect(openSpy.called).to.be.false;
  });

  it('should focus the trigger after closing the popover', function (done) {
    const openSpy = sinon.fake();

    renderPopover({
      open: true,
      setOpen: openSpy,
    });

    const triggerButton = screen.getByTestId('trigger-button');
    triggerButton.addEventListener('focus', () => {
      done();
    });

    const closeButton = screen.getByTestId('interactive-popover-close-button');
    closeButton.click();
  });
});
